use crate::cmd::{validate_command, CommandError, CommandExecutor, Get, Set, RSP_OK};
use crate::{RespArray, RespFrame, RespNull};

impl CommandExecutor for Get {
    fn execute(self, backend: &crate::Backend) -> RespFrame {
        match backend.get(&self.key) {
            Some(value) => value,
            None => RespFrame::Null(RespNull),
        }
    }
}

impl CommandExecutor for Set {
    fn execute(self, backend: &crate::Backend) -> RespFrame {
        backend.set(self.key, self.value);
        RSP_OK.clone()
    }
}

impl TryFrom<RespArray> for Get {
    type Error = CommandError;
    fn try_from(value: RespArray) -> Result<Self, Self::Error> {
        validate_command(&value, "get", 1)?;
        let key = match value[1] {
            RespFrame::BulkString(ref key) => match String::from_utf8(key.to_vec()) {
                Ok(key) => key,
                Err(_) => {
                    return Err(CommandError::InvalidArgs(
                        "Get command must be have a key as the first argument".to_string(),
                    ))
                }
            },
            _ => {
                return Err(CommandError::InvalidCommand(
                    "Get command must have a invalid key".to_string(),
                ));
            }
        };

        Ok(Get { key })
    }
}

impl TryFrom<RespArray> for Set {
    type Error = CommandError;
    fn try_from(value: RespArray) -> Result<Self, Self::Error> {
        validate_command(&value, "set", 2)?;
        match (&value[1], &value[2]) {
            (RespFrame::BulkString(ref key), value) => match String::from_utf8(key.to_vec()) {
                Ok(key) => Ok(Set {
                    key,
                    value: value.clone(),
                }),
                Err(_) => Err(CommandError::InvalidArgs(
                    "Set command must be have a key as the first argument".to_string(),
                )),
            },
            _ => Err(CommandError::InvalidCommand(
                "Set command must have invalid key and value".to_string(),
            )),
        }
    }
}

#[cfg(test)]
mod tests {
    use bytes::BytesMut;

    use crate::{cmd::RSP_OK, Backend, BulkString, RespDecode};

    use super::*;

    #[test]
    fn get_try_from_array_test() -> anyhow::Result<()> {
        let mut buf = BytesMut::new();
        buf.extend_from_slice(b"*2\r\n$3\r\nget\r\n$5\r\nhello\r\n");

        let frame = RespArray::decode(&mut buf)?;

        let ans: Get = frame.try_into()?;

        assert_eq!(ans.key, "hello");

        Ok(())
    }

    #[test]
    fn set_try_from_array_test() -> anyhow::Result<()> {
        let mut buf = BytesMut::new();
        buf.extend_from_slice(b"*3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n");

        let frame = RespArray::decode(&mut buf)?;
        let ans: Set = frame.try_into()?;

        assert_eq!(ans.key, "hello");
        assert_eq!(ans.value, BulkString::new(b"world").into());

        Ok(())
    }

    #[test]
    fn set_of_get_command_test() -> anyhow::Result<()> {
        let backend = Backend::new();
        let cmd = Set {
            key: "hello".to_string(),
            value: BulkString::new(b"world").into(),
        };

        let ans = cmd.execute(&backend);
        assert_eq!(ans, RSP_OK.clone());

        let cmd = Get {
            key: "hello".to_string(),
        };

        let ans = cmd.execute(&backend);
        assert_eq!(ans, BulkString::new("world").into());

        Ok(())
    }
}
